comark 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/internal/frontmatter.d.ts +1 -0
- package/dist/internal/frontmatter.js +4 -2
- package/dist/internal/parse/auto-close/index.js +69 -31
- package/dist/internal/parse/auto-close/table.js +12 -9
- package/dist/internal/parse/auto-unwrap.js +6 -10
- package/dist/internal/parse/html/html_block_rule.js +10 -16
- package/dist/internal/parse/html/html_inline_rule.js +3 -7
- package/dist/internal/parse/html/html_re.js +1 -1
- package/dist/internal/parse/html/index.d.ts +1 -0
- package/dist/internal/parse/html/index.js +15 -3
- package/dist/internal/parse/syntax/block-params.d.ts +9 -0
- package/dist/internal/parse/syntax/block-params.js +48 -0
- package/dist/internal/parse/syntax/brackets.d.ts +8 -0
- package/dist/internal/parse/syntax/brackets.js +20 -0
- package/dist/internal/parse/syntax/props.d.ts +5 -0
- package/dist/internal/parse/syntax/props.js +119 -0
- package/dist/internal/parse/token-processor.js +89 -50
- package/dist/internal/props-validation.js +4 -9
- package/dist/internal/stringify/attributes.d.ts +7 -0
- package/dist/internal/stringify/attributes.js +56 -1
- package/dist/internal/stringify/handlers/a.js +1 -3
- package/dist/internal/stringify/handlers/blockquote.js +19 -4
- package/dist/internal/stringify/handlers/code.js +1 -3
- package/dist/internal/stringify/handlers/emphesis.js +1 -3
- package/dist/internal/stringify/handlers/heading.js +6 -1
- package/dist/internal/stringify/handlers/html.js +34 -18
- package/dist/internal/stringify/handlers/img.js +1 -3
- package/dist/internal/stringify/handlers/li.js +18 -9
- package/dist/internal/stringify/handlers/mdc.js +3 -4
- package/dist/internal/stringify/handlers/ol.js +12 -2
- package/dist/internal/stringify/handlers/p.d.ts +1 -1
- package/dist/internal/stringify/handlers/p.js +8 -1
- package/dist/internal/stringify/handlers/pre.js +20 -14
- package/dist/internal/stringify/handlers/strong.js +1 -3
- package/dist/internal/stringify/handlers/table.js +14 -5
- package/dist/internal/stringify/handlers/template.js +5 -2
- package/dist/internal/stringify/handlers/ul.js +12 -2
- package/dist/internal/stringify/state.js +1 -1
- package/dist/internal/yaml.js +1 -1
- package/dist/parse.d.ts +4 -4
- package/dist/parse.js +20 -10
- package/dist/plugins/alert.d.ts +1 -1
- package/dist/plugins/alert.js +1 -1
- package/dist/plugins/binding.d.ts +1 -1
- package/dist/plugins/binding.js +1 -3
- package/dist/plugins/breaks.d.ts +1 -1
- package/dist/plugins/breaks.js +1 -1
- package/dist/plugins/emoji.d.ts +1 -1
- package/dist/plugins/emoji.js +8 -8
- package/dist/plugins/footnotes.d.ts +1 -1
- package/dist/plugins/footnotes.js +19 -13
- package/dist/plugins/headings.d.ts +19 -8
- package/dist/plugins/headings.js +27 -19
- package/dist/plugins/highlight.d.ts +2 -12
- package/dist/plugins/highlight.js +201 -103
- package/dist/plugins/json-render.d.ts +1 -1
- package/dist/plugins/json-render.js +5 -9
- package/dist/plugins/math.d.ts +1 -1
- package/dist/plugins/math.js +4 -6
- package/dist/plugins/mermaid.d.ts +1 -1
- package/dist/plugins/mermaid.js +6 -20
- package/dist/plugins/punctuation.d.ts +1 -1
- package/dist/plugins/punctuation.js +5 -6
- package/dist/plugins/security.d.ts +1 -1
- package/dist/plugins/security.js +2 -2
- package/dist/plugins/summary.d.ts +4 -1
- package/dist/plugins/syntax.d.ts +49 -0
- package/dist/plugins/syntax.js +558 -0
- package/dist/plugins/task-list.d.ts +2 -2
- package/dist/plugins/task-list.js +11 -8
- package/dist/plugins/toc.d.ts +3 -1
- package/dist/plugins/toc.js +1 -1
- package/dist/types.d.ts +57 -12
- package/dist/utils/comark.tmLanguage.d.ts +335 -0
- package/dist/utils/comark.tmLanguage.js +597 -0
- package/dist/utils/helpers.d.ts +16 -4
- package/dist/utils/helpers.js +16 -6
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.js +25 -3
- package/package.json +40 -40
- package/skills/comark/references/rendering-svelte.md +51 -7
- package/dist/internal/stringify/indent.d.ts +0 -5
- package/dist/internal/stringify/indent.js +0 -9
- package/dist/vite.d.ts +0 -1
- package/dist/vite.js +0 -1
- package/skills/skills/comark/AGENTS.md +0 -261
- package/skills/skills/comark/SKILL.md +0 -489
- package/skills/skills/comark/references/markdown-syntax.md +0 -599
- package/skills/skills/comark/references/parsing-ast.md +0 -378
- package/skills/skills/comark/references/rendering-react.md +0 -445
- package/skills/skills/comark/references/rendering-svelte.md +0 -453
- package/skills/skills/comark/references/rendering-vue.md +0 -462
- /package/skills/{skills/migrate-mdc-to-comark → migrate-mdc-to-comark}/SKILL.md +0 -0
|
@@ -9,11 +9,12 @@ const CR = '\r';
|
|
|
9
9
|
*/
|
|
10
10
|
export function parseFrontmatter(content) {
|
|
11
11
|
let data = {};
|
|
12
|
+
let frontmatter = '';
|
|
12
13
|
if (content.startsWith(FRONTMATTER_DELIMITER_DEFAULT)) {
|
|
13
14
|
const idx = content.indexOf(LF + FRONTMATTER_DELIMITER_DEFAULT);
|
|
14
15
|
if (idx !== -1) {
|
|
15
16
|
const hasCarriageReturn = content[idx - 1] === CR;
|
|
16
|
-
|
|
17
|
+
frontmatter = content.slice(4, idx - (hasCarriageReturn ? 1 : 0));
|
|
17
18
|
if (frontmatter) {
|
|
18
19
|
data = parseYaml(frontmatter);
|
|
19
20
|
content = content.slice(idx + 4 + (hasCarriageReturn ? 1 : 0));
|
|
@@ -23,6 +24,7 @@ export function parseFrontmatter(content) {
|
|
|
23
24
|
return {
|
|
24
25
|
content,
|
|
25
26
|
data,
|
|
27
|
+
frontmatterText: frontmatter,
|
|
26
28
|
};
|
|
27
29
|
}
|
|
28
30
|
/**
|
|
@@ -33,7 +35,7 @@ export function parseFrontmatter(content) {
|
|
|
33
35
|
*/
|
|
34
36
|
export function renderFrontmatter(data, content, yamlOptions) {
|
|
35
37
|
if (!data || Object.keys(data).length === 0) {
|
|
36
|
-
return
|
|
38
|
+
return content?.trim() || '';
|
|
37
39
|
}
|
|
38
40
|
const fm = stringifyYaml(data, yamlOptions).trim();
|
|
39
41
|
if (content) {
|
|
@@ -19,10 +19,33 @@ export function autoCloseMarkdown(markdown) {
|
|
|
19
19
|
let inFrontmatter = false;
|
|
20
20
|
let inBlockMath = false;
|
|
21
21
|
let tableStart = -1;
|
|
22
|
+
// Tag name when inside a raw-text HTML element (`<style>`, `<script>`,
|
|
23
|
+
// `<pre>`, `<textarea>`). Their bodies must be passed through verbatim —
|
|
24
|
+
// any `::root`/`**` markers there are CSS/JS/text, not Comark/markdown.
|
|
25
|
+
let inRawTextElement = null;
|
|
26
|
+
const RAW_TEXT_OPEN_RE = /^<(script|pre|style|textarea)(\s|>|$)/i;
|
|
22
27
|
const componentStack = [];
|
|
23
28
|
for (let idx = 0; idx < n; idx++) {
|
|
24
29
|
const line = lines[idx];
|
|
25
30
|
const trimmed = line.trim();
|
|
31
|
+
// Raw-text HTML element: skip all line-level processing inside its body,
|
|
32
|
+
// and update the open/close state. Open and close can sit on the same
|
|
33
|
+
// line (e.g. `<style>body { ... }</style>` inline).
|
|
34
|
+
if (inRawTextElement) {
|
|
35
|
+
const closeRe = new RegExp(`</${inRawTextElement}\\s*>`, 'i');
|
|
36
|
+
if (closeRe.test(line))
|
|
37
|
+
inRawTextElement = null;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const rawTextMatch = trimmed.match(RAW_TEXT_OPEN_RE);
|
|
41
|
+
if (rawTextMatch) {
|
|
42
|
+
const tag = rawTextMatch[1].toLowerCase();
|
|
43
|
+
// Stay "inside" only if the close tag isn't already on this line.
|
|
44
|
+
const closeRe = new RegExp(`</${tag}\\s*>`, 'i');
|
|
45
|
+
if (!closeRe.test(line))
|
|
46
|
+
inRawTextElement = tag;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
26
49
|
// Frontmatter: only starts at document line 0
|
|
27
50
|
if (idx === 0 && trimmed === '---') {
|
|
28
51
|
inFrontmatter = true;
|
|
@@ -77,11 +100,22 @@ export function autoCloseMarkdown(markdown) {
|
|
|
77
100
|
let nameEnd = colonCount;
|
|
78
101
|
while (nameEnd < trimmed.length) {
|
|
79
102
|
const c = trimmed[nameEnd];
|
|
80
|
-
if (!((c >= 'a' && c <= 'z') ||
|
|
103
|
+
if (!((c >= 'a' && c <= 'z') ||
|
|
104
|
+
(c >= 'A' && c <= 'Z') ||
|
|
105
|
+
(c >= '0' && c <= '9') ||
|
|
106
|
+
c === '$' ||
|
|
107
|
+
c === '.' ||
|
|
108
|
+
c === '-' ||
|
|
109
|
+
c === '_'))
|
|
81
110
|
break;
|
|
82
111
|
nameEnd++;
|
|
83
112
|
}
|
|
84
|
-
componentStack.push({
|
|
113
|
+
componentStack.push({
|
|
114
|
+
depth: colonCount,
|
|
115
|
+
name: trimmed.slice(colonCount, nameEnd),
|
|
116
|
+
indent,
|
|
117
|
+
hasYamlProps: false,
|
|
118
|
+
});
|
|
85
119
|
}
|
|
86
120
|
else if (colonCount === trimmed.length && componentStack.length > 0) {
|
|
87
121
|
const top = componentStack[componentStack.length - 1];
|
|
@@ -136,14 +170,14 @@ export function autoCloseMarkdown(markdown) {
|
|
|
136
170
|
for (let i = 0; i < propsContent.length; i++) {
|
|
137
171
|
if (propsContent[i] === '"')
|
|
138
172
|
dq++;
|
|
139
|
-
if (propsContent[i] === '
|
|
173
|
+
if (propsContent[i] === "'")
|
|
140
174
|
sq++;
|
|
141
175
|
}
|
|
142
176
|
let braceClose = '';
|
|
143
177
|
if (dq % 2 === 1)
|
|
144
178
|
braceClose += '"';
|
|
145
179
|
if (sq % 2 === 1)
|
|
146
|
-
braceClose += '
|
|
180
|
+
braceClose += "'";
|
|
147
181
|
result += braceClose + '}';
|
|
148
182
|
}
|
|
149
183
|
if (componentStack.length > 0) {
|
|
@@ -179,7 +213,8 @@ function closeInlineMarkersLinear(line) {
|
|
|
179
213
|
// Count markers by scanning
|
|
180
214
|
let asteriskCount = 0;
|
|
181
215
|
let underscoreCount = 0;
|
|
182
|
-
let
|
|
216
|
+
let doubleTildeCount = 0; // Count ~~ occurrences (GFM strikethrough delimiter)
|
|
217
|
+
let singleTildeCount = 0; // Count standalone ~ (not part of ~~)
|
|
183
218
|
let backtickCount = 0;
|
|
184
219
|
let dollarCount = 0; // Count $ for math
|
|
185
220
|
let dollarPairCount = 0; // Count $$ pairs for block math
|
|
@@ -269,7 +304,13 @@ function closeInlineMarkersLinear(line) {
|
|
|
269
304
|
}
|
|
270
305
|
}
|
|
271
306
|
else if (ch === '~') {
|
|
272
|
-
|
|
307
|
+
if (i + 1 < len && line[i + 1] === '~') {
|
|
308
|
+
doubleTildeCount++;
|
|
309
|
+
i++; // Skip second tilde since we counted the pair
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
singleTildeCount++;
|
|
313
|
+
}
|
|
273
314
|
}
|
|
274
315
|
else if (ch === '`') {
|
|
275
316
|
backtickCount++;
|
|
@@ -299,8 +340,11 @@ function closeInlineMarkersLinear(line) {
|
|
|
299
340
|
// Check if line starts with more than 3 asterisks (e.g., ****)
|
|
300
341
|
if (!(line[3] === '*')) {
|
|
301
342
|
// Check if marker at end with no content
|
|
302
|
-
if (!(contentEnd >= 3 &&
|
|
303
|
-
|
|
343
|
+
if (!(contentEnd >= 3 &&
|
|
344
|
+
line[contentEnd - 1] === '*' &&
|
|
345
|
+
line[contentEnd - 2] === '*' &&
|
|
346
|
+
line[contentEnd - 3] === '*' &&
|
|
347
|
+
(contentEnd === 3 || line[contentEnd - 4] === ' '))) {
|
|
304
348
|
closingSuffix = '***';
|
|
305
349
|
}
|
|
306
350
|
}
|
|
@@ -333,9 +377,9 @@ function closeInlineMarkersLinear(line) {
|
|
|
333
377
|
if (!allPaired) {
|
|
334
378
|
// Check if line ends with word (not just a closing marker)
|
|
335
379
|
const lastChar = line[contentEnd - 1];
|
|
336
|
-
const endsWithWord = (lastChar >= 'a' && lastChar <= 'z')
|
|
337
|
-
|
|
338
|
-
|
|
380
|
+
const endsWithWord = (lastChar >= 'a' && lastChar <= 'z') ||
|
|
381
|
+
(lastChar >= 'A' && lastChar <= 'Z') ||
|
|
382
|
+
(lastChar >= '0' && lastChar <= '9');
|
|
339
383
|
if (!hasCompleteBoldPair || endsWithWord) {
|
|
340
384
|
closingSuffix = '**';
|
|
341
385
|
if (hasTrailingSpace && !endsWithMarker) {
|
|
@@ -375,8 +419,7 @@ function closeInlineMarkersLinear(line) {
|
|
|
375
419
|
// Check marker at end with no content
|
|
376
420
|
// Only skip if it's truly isolated (e.g., "input *")
|
|
377
421
|
// Don't skip if there are complete pairs before it (e.g., "input **bold** *")
|
|
378
|
-
const markerAtEnd = contentEnd >= 1 && line[contentEnd - 1] === '*'
|
|
379
|
-
&& (contentEnd === 1 || line[contentEnd - 2] === ' ');
|
|
422
|
+
const markerAtEnd = contentEnd >= 1 && line[contentEnd - 1] === '*' && (contentEnd === 1 || line[contentEnd - 2] === ' ');
|
|
380
423
|
if (!markerAtEnd || asteriskCount > 1) {
|
|
381
424
|
closingSuffix = '*';
|
|
382
425
|
const endsWithMarker = line[contentEnd - 1] === '*';
|
|
@@ -427,8 +470,7 @@ function closeInlineMarkersLinear(line) {
|
|
|
427
470
|
}
|
|
428
471
|
if (validItalic) {
|
|
429
472
|
// Check marker at end with no content
|
|
430
|
-
const markerAtEnd = contentEnd >= 1 && line[contentEnd - 1] === '_'
|
|
431
|
-
&& (contentEnd === 1 || line[contentEnd - 2] === ' ');
|
|
473
|
+
const markerAtEnd = contentEnd >= 1 && line[contentEnd - 1] === '_' && (contentEnd === 1 || line[contentEnd - 2] === ' ');
|
|
432
474
|
if (!markerAtEnd) {
|
|
433
475
|
closingSuffix = '_';
|
|
434
476
|
const endsWithMarker = line[contentEnd - 1] === '_';
|
|
@@ -438,22 +480,18 @@ function closeInlineMarkersLinear(line) {
|
|
|
438
480
|
}
|
|
439
481
|
}
|
|
440
482
|
}
|
|
441
|
-
// Check ~~ (strikethrough)
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
closingSuffix = '~'.repeat(needed);
|
|
454
|
-
if (hasTrailingSpace)
|
|
455
|
-
shouldTrim = true;
|
|
456
|
-
}
|
|
483
|
+
// Check ~~ (strikethrough) and ~ (single-tilde) separately so that paired
|
|
484
|
+
// singles like ~Hello~ are left alone while ~~text and ~Hello both close.
|
|
485
|
+
if (!closingSuffix && doubleTildeCount % 2 === 1) {
|
|
486
|
+
// A trailing single ~ after an open ~~ is a partial closer (~~text~)
|
|
487
|
+
closingSuffix = singleTildeCount === 1 ? '~' : '~~';
|
|
488
|
+
if (hasTrailingSpace)
|
|
489
|
+
shouldTrim = true;
|
|
490
|
+
}
|
|
491
|
+
else if (!closingSuffix && singleTildeCount % 2 === 1) {
|
|
492
|
+
closingSuffix = '~';
|
|
493
|
+
if (hasTrailingSpace)
|
|
494
|
+
shouldTrim = true;
|
|
457
495
|
}
|
|
458
496
|
// Check ` (code)
|
|
459
497
|
if (!closingSuffix && backtickCount % 2 === 1) {
|
|
@@ -87,12 +87,10 @@ export function closeTables(markdown) {
|
|
|
87
87
|
const generateSeparator = () => '| ' + Array(columnCount).fill('---').join(' | ') + ' |';
|
|
88
88
|
// Check if separator exists (including incomplete ones with just :)
|
|
89
89
|
const secondLine = end - start >= 1 ? lines[start + 1].trim() : '';
|
|
90
|
-
const hasSeparator = secondLine.startsWith('|')
|
|
91
|
-
&& (secondLine.includes('-') || secondLine.includes(':'));
|
|
90
|
+
const hasSeparator = secondLine.startsWith('|') && (secondLine.includes('-') || secondLine.includes(':'));
|
|
92
91
|
// Handle last line
|
|
93
92
|
const lastLine = lines[end].trim();
|
|
94
|
-
const isSeparator = lastLine.startsWith('|')
|
|
95
|
-
&& (lastLine.includes('-') || lastLine.includes(':'));
|
|
93
|
+
const isSeparator = lastLine.startsWith('|') && (lastLine.includes('-') || lastLine.includes(':'));
|
|
96
94
|
if (isSeparator) {
|
|
97
95
|
// Parse and complete separator cells
|
|
98
96
|
const sepCells = parseCells(lastLine);
|
|
@@ -147,11 +145,16 @@ export function closeTables(markdown) {
|
|
|
147
145
|
const refWidths = parseCellWidths(refRow);
|
|
148
146
|
const cells = parseCells(lastLine);
|
|
149
147
|
// Rebuild with padding
|
|
150
|
-
lines[end] =
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
148
|
+
lines[end] =
|
|
149
|
+
'| ' +
|
|
150
|
+
cells
|
|
151
|
+
.map((cell, i) => {
|
|
152
|
+
const targetWidth = refWidths[i] || cell.length + 2;
|
|
153
|
+
const padding = ' '.repeat(Math.max(0, targetWidth - cell.length - 2));
|
|
154
|
+
return cell + padding;
|
|
155
|
+
})
|
|
156
|
+
.join(' | ') +
|
|
157
|
+
' |';
|
|
155
158
|
}
|
|
156
159
|
// Add separator if missing
|
|
157
160
|
if (!hasSeparator) {
|
|
@@ -28,15 +28,11 @@ export function applyAutoUnwrap(node) {
|
|
|
28
28
|
}
|
|
29
29
|
// Check if we have exactly one paragraph child (and possibly empty text nodes)
|
|
30
30
|
if (nonEmptyChildren.length > 1 || typeof nonEmptyChildren[0] === 'string' || nonEmptyChildren[0][0] !== 'p') {
|
|
31
|
-
return [
|
|
32
|
-
tag,
|
|
33
|
-
props,
|
|
34
|
-
...children.map((child) => applyAutoUnwrap(child)),
|
|
35
|
-
];
|
|
31
|
+
return [tag, props, ...children.map((child) => applyAutoUnwrap(child))];
|
|
36
32
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
];
|
|
33
|
+
// Lift the paragraph's attrs onto the parent so trailing `{attr}` survives the unwrap.
|
|
34
|
+
// Parent attrs take precedence so explicit component props aren't overridden.
|
|
35
|
+
const paragraphAttrs = nonEmptyChildren[0][1];
|
|
36
|
+
const mergedProps = paragraphAttrs && Object.keys(paragraphAttrs).length > 0 ? { ...paragraphAttrs, ...props } : props;
|
|
37
|
+
return [tag, mergedProps, ...nonEmptyChildren[0].slice(2)];
|
|
42
38
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Standard CommonMark html_block rule — see
|
|
2
|
+
// https://spec.commonmark.org/0.30/#html-blocks
|
|
3
|
+
//
|
|
4
|
+
// 7 sequences in priority order, each: [opener regex, closer regex, can-terminate-paragraph]
|
|
2
5
|
import block_names from "./html_blocks.js";
|
|
3
6
|
import { HTML_OPEN_CLOSE_TAG_RE } from "./html_re.js";
|
|
4
|
-
// An array of opening and corresponding closing sequences for html tags,
|
|
5
|
-
// last argument defines whether it can terminate a paragraph or not
|
|
6
|
-
//
|
|
7
7
|
const HTML_SEQUENCES = [
|
|
8
|
-
[new RegExp(`${HTML_OPEN_CLOSE_TAG_RE.source}\\s*$`), /^<\/[^>]+>$/, true],
|
|
9
8
|
[/^<(script|pre|style|textarea)(?=(\s|>|$))/i, /<\/(script|pre|style|textarea)>/i, true],
|
|
10
9
|
[/^<!--/, /-->/, true],
|
|
11
10
|
[/^<\?/, /\?>/, true],
|
|
@@ -17,10 +16,9 @@ const HTML_SEQUENCES = [
|
|
|
17
16
|
export default function html_block(state, startLine, endLine, silent) {
|
|
18
17
|
let pos = state.bMarks[startLine] + state.tShift[startLine];
|
|
19
18
|
let max = state.eMarks[startLine];
|
|
20
|
-
// if it's indented more than 3 spaces, it should be a code block
|
|
21
19
|
if (state.sCount[startLine] - state.blkIndent >= 4)
|
|
22
20
|
return false;
|
|
23
|
-
if (state.src.charCodeAt(pos) !==
|
|
21
|
+
if (state.src.charCodeAt(pos) !== 0x3c /* < */)
|
|
24
22
|
return false;
|
|
25
23
|
let lineText = state.src.slice(pos, max);
|
|
26
24
|
let i = 0;
|
|
@@ -30,18 +28,14 @@ export default function html_block(state, startLine, endLine, silent) {
|
|
|
30
28
|
}
|
|
31
29
|
if (i === HTML_SEQUENCES.length)
|
|
32
30
|
return false;
|
|
33
|
-
if (silent)
|
|
34
|
-
// true if this sequence can be a terminator, false otherwise
|
|
31
|
+
if (silent)
|
|
35
32
|
return HTML_SEQUENCES[i][2];
|
|
36
|
-
}
|
|
37
33
|
let nextLine = startLine + 1;
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
if (i !== 0 && !HTML_SEQUENCES[i][1].test(lineText)) {
|
|
34
|
+
// Walk forward until the closer regex matches or we hit a blank line.
|
|
35
|
+
if (!HTML_SEQUENCES[i][1].test(lineText)) {
|
|
41
36
|
for (; nextLine < endLine; nextLine++) {
|
|
42
|
-
if (state.sCount[nextLine] < state.blkIndent)
|
|
37
|
+
if (state.sCount[nextLine] < state.blkIndent)
|
|
43
38
|
break;
|
|
44
|
-
}
|
|
45
39
|
pos = state.bMarks[nextLine] + state.tShift[nextLine];
|
|
46
40
|
max = state.eMarks[nextLine];
|
|
47
41
|
lineText = state.src.slice(pos, max);
|
|
@@ -53,7 +47,7 @@ export default function html_block(state, startLine, endLine, silent) {
|
|
|
53
47
|
}
|
|
54
48
|
}
|
|
55
49
|
state.line = nextLine;
|
|
56
|
-
const token =
|
|
50
|
+
const token = state.push('html_block', '', 1);
|
|
57
51
|
token.map = [startLine, nextLine];
|
|
58
52
|
token.content = state.getLines(startLine, nextLine, state.blkIndent, true);
|
|
59
53
|
return true;
|
|
@@ -9,22 +9,18 @@ function isLinkClose(str) {
|
|
|
9
9
|
function isLetter(ch) {
|
|
10
10
|
/* eslint no-bitwise:0 */
|
|
11
11
|
const lc = ch | 0x20; // to lower case
|
|
12
|
-
return
|
|
12
|
+
return lc >= 0x61 /* a */ && lc <= 0x7a; /* z */
|
|
13
13
|
}
|
|
14
14
|
export default function html_inline(state, silent) {
|
|
15
15
|
// Check start
|
|
16
16
|
const max = state.posMax;
|
|
17
17
|
const pos = state.pos;
|
|
18
|
-
if (state.src.charCodeAt(pos) !==
|
|
19
|
-
|| /* < */ pos + 2 >= max) {
|
|
18
|
+
if (state.src.charCodeAt(pos) !== 0x3c || /* < */ pos + 2 >= max) {
|
|
20
19
|
return false;
|
|
21
20
|
}
|
|
22
21
|
// Quick fail on second char
|
|
23
22
|
const ch = state.src.charCodeAt(pos + 1);
|
|
24
|
-
if (ch !== 0x21
|
|
25
|
-
&& /* ! */ ch !== 0x3F
|
|
26
|
-
&& /* ? */ ch !== 0x2F
|
|
27
|
-
&& /* / */ !isLetter(ch)) {
|
|
23
|
+
if (ch !== 0x21 && /* ! */ ch !== 0x3f && /* ? */ ch !== 0x2f && /* / */ !isLetter(ch)) {
|
|
28
24
|
return false;
|
|
29
25
|
}
|
|
30
26
|
const match = state.src.slice(pos).match(HTML_TAG_RE);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Regexps to match html elements
|
|
2
2
|
const attr_name = '[a-zA-Z_:][a-zA-Z0-9:._-]*';
|
|
3
3
|
const unquoted = '[^"\'=<>`\\x00-\\x20]+';
|
|
4
|
-
const single_quoted = '
|
|
4
|
+
const single_quoted = "'[^']*'";
|
|
5
5
|
const double_quoted = '"[^"]*"';
|
|
6
6
|
const attr_value = `(?:${unquoted}|${single_quoted}|${double_quoted})`;
|
|
7
7
|
const attribute = `(?:\\s+${attr_name}(?:\\s*=\\s*${attr_value})?)`;
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import { Parser } from 'htmlparser2';
|
|
2
|
-
const VOID_ELEMENTS = new Set([
|
|
3
|
-
'area',
|
|
4
|
-
'
|
|
2
|
+
export const VOID_ELEMENTS = new Set([
|
|
3
|
+
'area',
|
|
4
|
+
'base',
|
|
5
|
+
'br',
|
|
6
|
+
'col',
|
|
7
|
+
'embed',
|
|
8
|
+
'hr',
|
|
9
|
+
'img',
|
|
10
|
+
'input',
|
|
11
|
+
'link',
|
|
12
|
+
'meta',
|
|
13
|
+
'param',
|
|
14
|
+
'source',
|
|
15
|
+
'track',
|
|
16
|
+
'wbr',
|
|
5
17
|
]);
|
|
6
18
|
function attribsToComarkAttrs(attribs, isInline = false) {
|
|
7
19
|
const attrs = {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { kebabCase } from "../../../utils/index.js";
|
|
2
|
+
import { parseBracketContent } from "./brackets.js";
|
|
3
|
+
import { searchProps } from "./props.js";
|
|
4
|
+
const RE_BLOCK_NAME = /^[a-z$][$\w.-]*/i;
|
|
5
|
+
/**
|
|
6
|
+
* Parse `component-name [content] {.params}` from the trailing portion of a block fence line.
|
|
7
|
+
*/
|
|
8
|
+
export function parseBlockParams(str) {
|
|
9
|
+
str = str.trim();
|
|
10
|
+
if (!str)
|
|
11
|
+
return { name: '' };
|
|
12
|
+
const name = str.match(RE_BLOCK_NAME)?.[0];
|
|
13
|
+
if (!name)
|
|
14
|
+
throw new Error(`Invalid block params: ${str}`);
|
|
15
|
+
let remaining = str.slice(name.length).trim();
|
|
16
|
+
let content;
|
|
17
|
+
let props;
|
|
18
|
+
let unparsedRemaining;
|
|
19
|
+
if (remaining.startsWith('[')) {
|
|
20
|
+
const result = parseBracketContent(remaining, 0);
|
|
21
|
+
if (result) {
|
|
22
|
+
content = result.content;
|
|
23
|
+
remaining = remaining.slice(result.endIndex).trim();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (remaining.startsWith('{')) {
|
|
27
|
+
const propsResult = searchProps(remaining, 0);
|
|
28
|
+
if (propsResult) {
|
|
29
|
+
props = propsResult.props;
|
|
30
|
+
const afterProps = remaining.slice(propsResult.index).trim();
|
|
31
|
+
if (afterProps)
|
|
32
|
+
unparsedRemaining = afterProps;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else if (remaining) {
|
|
36
|
+
unparsedRemaining = remaining;
|
|
37
|
+
}
|
|
38
|
+
const result = {
|
|
39
|
+
name: kebabCase(name),
|
|
40
|
+
};
|
|
41
|
+
if (content !== undefined)
|
|
42
|
+
result.content = content;
|
|
43
|
+
if (props !== undefined)
|
|
44
|
+
result.props = props;
|
|
45
|
+
if (unparsedRemaining)
|
|
46
|
+
result.remaining = unparsedRemaining;
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse content within square brackets `[content]`.
|
|
3
|
+
* Returns the content (without the brackets) and the index just past the closing `]`.
|
|
4
|
+
*/
|
|
5
|
+
export declare function parseBracketContent(str: string, startIndex: number): {
|
|
6
|
+
content: string;
|
|
7
|
+
endIndex: number;
|
|
8
|
+
} | null;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse content within square brackets `[content]`.
|
|
3
|
+
* Returns the content (without the brackets) and the index just past the closing `]`.
|
|
4
|
+
*/
|
|
5
|
+
export function parseBracketContent(str, startIndex) {
|
|
6
|
+
if (str[startIndex] !== '[')
|
|
7
|
+
return null;
|
|
8
|
+
let index = startIndex + 1;
|
|
9
|
+
while (index < str.length) {
|
|
10
|
+
if (str[index] === '\\' && index + 1 < str.length) {
|
|
11
|
+
index += 2;
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (str[index] === ']') {
|
|
15
|
+
return { content: str.slice(startIndex + 1, index), endIndex: index + 1 };
|
|
16
|
+
}
|
|
17
|
+
index += 1;
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
const bracketPairs = {
|
|
2
|
+
'[': ']',
|
|
3
|
+
'{': '}',
|
|
4
|
+
'(': ')',
|
|
5
|
+
};
|
|
6
|
+
const quotePairs = {
|
|
7
|
+
"'": "'",
|
|
8
|
+
'"': '"',
|
|
9
|
+
'`': '`',
|
|
10
|
+
};
|
|
11
|
+
export function parseProps(content) {
|
|
12
|
+
content = content.trim();
|
|
13
|
+
if (!content)
|
|
14
|
+
return undefined;
|
|
15
|
+
const props = searchProps(content);
|
|
16
|
+
if (!props)
|
|
17
|
+
throw new Error(`Invalid props: \`${content}\``);
|
|
18
|
+
if (props.index !== content.length)
|
|
19
|
+
throw new Error(`Invalid props: \`${content}\`, expected end \`}\` but got \`${content.slice(props.index)}\``);
|
|
20
|
+
return props.props;
|
|
21
|
+
}
|
|
22
|
+
export function searchProps(content, index = 0) {
|
|
23
|
+
if (content[index] !== '{')
|
|
24
|
+
throw new Error(`Invalid props, expected \`{\` but got '${content[index]}'`);
|
|
25
|
+
const props = [];
|
|
26
|
+
// Skip Vue mustache `{{ }}` syntax
|
|
27
|
+
if (content[index + 1] === '{')
|
|
28
|
+
return undefined;
|
|
29
|
+
index += 1;
|
|
30
|
+
while (index < content.length) {
|
|
31
|
+
if (content[index] === '\\') {
|
|
32
|
+
index += 2;
|
|
33
|
+
}
|
|
34
|
+
else if (content[index] === '}') {
|
|
35
|
+
index += 1;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
else if (content[index] === ' ') {
|
|
39
|
+
index += 1;
|
|
40
|
+
}
|
|
41
|
+
else if (content[index] === '.') {
|
|
42
|
+
index += 1;
|
|
43
|
+
props.push(['class', searchUntil(' #.}')]);
|
|
44
|
+
}
|
|
45
|
+
else if (content[index] === '#') {
|
|
46
|
+
index += 1;
|
|
47
|
+
props.push(['id', searchUntil(' #.}')]);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
const start = index;
|
|
51
|
+
while (index < content.length) {
|
|
52
|
+
index += 1;
|
|
53
|
+
if (' }='.includes(content[index]))
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
const char = content[index];
|
|
57
|
+
if (start !== index) {
|
|
58
|
+
const key = content.slice(start, index).trim();
|
|
59
|
+
if (char === '=') {
|
|
60
|
+
index += 1;
|
|
61
|
+
props.push([key, searchValue()]);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
props.push([key, 'true']);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function searchUntil(str) {
|
|
70
|
+
const start = index;
|
|
71
|
+
while (index < content.length) {
|
|
72
|
+
index += 1;
|
|
73
|
+
if (content[index] === '\\')
|
|
74
|
+
index += 2;
|
|
75
|
+
if (str.includes(content[index]))
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
return content.slice(start, index);
|
|
79
|
+
}
|
|
80
|
+
function searchValue() {
|
|
81
|
+
const start = index;
|
|
82
|
+
if (content[index] in bracketPairs) {
|
|
83
|
+
searchBracket(bracketPairs[content[index]]);
|
|
84
|
+
index += 1;
|
|
85
|
+
return content.slice(start, index);
|
|
86
|
+
}
|
|
87
|
+
else if (content[index] in quotePairs) {
|
|
88
|
+
searchString(quotePairs[content[index]]);
|
|
89
|
+
index += 1;
|
|
90
|
+
return content.slice(start, index);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
return searchUntil(' }');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function searchBracket(end) {
|
|
97
|
+
while (index < content.length) {
|
|
98
|
+
index++;
|
|
99
|
+
if (content[index] in quotePairs)
|
|
100
|
+
searchString(quotePairs[content[index]]);
|
|
101
|
+
else if (content[index] in bracketPairs)
|
|
102
|
+
searchBracket(bracketPairs[content[index]]);
|
|
103
|
+
else if (content[index] === end)
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function searchString(end) {
|
|
108
|
+
return searchUntil(end);
|
|
109
|
+
}
|
|
110
|
+
// Strip surrounding quotes from values
|
|
111
|
+
props.forEach((v) => {
|
|
112
|
+
if (/^(['"`]).*\1$/.test(v[1]))
|
|
113
|
+
v[1] = v[1].slice(1, -1);
|
|
114
|
+
});
|
|
115
|
+
return {
|
|
116
|
+
props,
|
|
117
|
+
index,
|
|
118
|
+
};
|
|
119
|
+
}
|